/**
 * @author Shine
 */
package com.shine.crypt
{
	import com.shine.events.AnimationClipEvent;
	import com.shine.ports.IAnimationClip;
	import com.shine.type.AnimationClipCommand;
	import com.shine.utils.ClassUtil;
	import com.shine.utils.DisplayObjectUtil;
	import com.shine.utils.StringUtil;
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	
	[Event(name="enterMovie", type="com.shine.events.AnimationClipEvent")]
	[Event(name="overMovie" , type="com.shine.events.AnimationClipEvent")]
	[Event(name="goToFrame" , type="com.shine.events.AnimationClipEvent")]
	[Event(name="goToLabel" , type="com.shine.events.AnimationClipEvent")]
	
	public class AnimationClip extends EventDispatcher implements IAnimationClip
	{
		protected var _cache:Vector.<DisplayObject>;
		protected var _frames:Vector.<uint>;
		protected var _points_prec:Vector.<uint>;
		protected var _points_next:Vector.<uint>;
		protected var _commands:Dictionary;
		
		protected var _myTimer:Timer;
		protected var _container:SpriteBase;
		protected var _current_child:DisplayObject;
		
		protected var _current_index:uint;
		protected var _current_count:uint;
		protected var _clockwise:Boolean;
		protected var _enabled:Boolean;
		protected var _skip:Boolean;
		
		protected var _max_width:Number;
		protected var _max_height:Number;

		public var loop:Boolean=true;
		
		public function AnimationClip()
		{
			super();
			initParams();
			_container.addEventListener(Event.ADDED_TO_STAGE,addToStageHandler,false,0,true);
			_container.addEventListener(Event.REMOVED_FROM_STAGE,removeToStageHandler,false,0,true);
		}
		
		public function get currentFrame():uint
		{
			return (_current_index+1);
		}
		
		public function get totalFrames():uint
		{
			return _frames.length;
		}
		
		public function get container():SpriteBase
		{
			return _container;
		}
		
		public function get maxWidth():Number
		{
			return _max_width;
		}
		
		public function get maxHeight():Number
		{
			return _max_height;
		}
		
		public function gotoAndPlay(frame:uint):void
		{
			if(frame<1 || frame>_frames.length) return;
			_current_index=frame-1;
			displayFrame(getFrameTarget());
			_skip=true;
			play();
		}
		
		public function gotoAndStop(frame:uint):void
		{
			if(frame<1 || frame>_frames.length) return;
			_current_index=frame-1;
			displayFrame(getFrameTarget());
			stop();
		}
		
		public function nextFrame():void
		{
			_current_index=_points_next[_current_index];
			displayFrame(getFrameTarget());
		}
		
		public function prevFrame():void
		{
			_current_index=_points_prec[_current_index];
			displayFrame(getFrameTarget());
		}
		
		public function play(clockwise:Boolean=true):void
		{
			_clockwise=clockwise;
			stop();
			
			_myTimer = new Timer(AnimationClip.interval,0);
			_myTimer.addEventListener(TimerEvent.TIMER,onEnterFrame,false,0,true);
			_myTimer.start();
		}
		
		public function stop():void
		{
			if(_myTimer.hasEventListener(TimerEvent.TIMER)){
				_myTimer.stop();
				_myTimer.removeEventListener(TimerEvent.TIMER,onEnterFrame);
				_skip=true;
			}
		}
		
		/**
		 * @param id is value of addChild return
		 * return first frame number
		 */
		public function getFrame(id:int):uint
		{
			var n:uint=_frames.indexOf(id);
			if(n==-1) return 0;
			return (n+1);
		}
		
		protected function onEnterFrame(evt:TimerEvent):void
		{	
			if(_skip){
				_skip=false;
				return;
			}
			
			if(_clockwise) nextFrame();
			else           prevFrame();
			
			this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.ENTER_FRAME,currentFrame));
			
			if(currentFrame==totalFrames)
			{
				this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.OVER_MOVIE,currentFrame));
				if(!loop) stop();
			}
		}
		
		protected function addToStageHandler(e:Event):void
		{
			_enabled=true;
			
			if(_cache.length>0)
			{
				displayFrame(getFrameTarget());
			}
		}
		
		protected function removeToStageHandler(e:Event):void
		{
			_enabled=false;
		}
		
		protected function displayFrame(child:DisplayObject):void
		{
			if(child==null || !_enabled) {
				_current_child=child;
				DisplayObjectUtil.removeAllChildren(_container);
				checkOrder(currentFrame);
				return;
			}
			
			var pass:Boolean=true;
			if(_container.numChildren>0){
				if(_current_child==child){
					pass=false;
					if(_current_child is MovieClip){
						_current_count=Math.abs(_current_index-_frames.indexOf(_frames[_current_index]));
						var length:uint=(_current_child as MovieClip).totalFrames;
						_current_count=_current_count>length ? (_current_count%length) : _current_count;
						if(_current_count>0)(_current_child as MovieClip).gotoAndStop(_current_count);
					}else{
						_current_count=0;
					}
				}else{
					DisplayObjectUtil.removeAllChildren(_container);
				}
			}
			
			if(pass){
				_container.addChild(child);
				_current_count=0;
			}
				
			_current_child=child;
			checkOrder(currentFrame);
		}
		
		protected function checkOrder(key:uint):void
		{
			if(_commands[key] != undefined){
				switch(_commands[key].type){
					case AnimationClipCommand.STOP:
						stop();
						break;
					
					case AnimationClipCommand.DISPATCH_EVENT:
						if(StringUtil.isEmpty(_commands[key].label)) this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.GO_TO_FRAME,currentFrame));
						else this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.GO_TO_LABEL,currentFrame,_commands[key].label));
						break;
					
					case AnimationClipCommand.STOP_AND_EVENT:
						stop();
						if(StringUtil.isEmpty(_commands[key].label)) this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.GO_TO_FRAME,currentFrame));
						else this.dispatchEvent(new AnimationClipEvent(AnimationClipEvent.GO_TO_LABEL,currentFrame,_commands[key].label));
						break;
					
					default:
						trace("AnimationClip:checkOrder undefined order :"+_commands[key]);
						break;
				}
			}
		}
		
		public function addChild(child:DisplayObject,frames:uint=1):Array
		{
			var id:uint=_cache.push(child);
			check_MC_label(child);
			for(var i:uint=0;i<frames;i++)_frames.push(id-1);
			if(child!=null)resetSizeValue(child);
			addDirectPoint();
			return [(id-1)];
		}
		
		public function removeChild(child:DisplayObject):void
		{
			if(child==null) return;
			var id:int=_cache.indexOf(child);
			if(id==-1) return;
			removeFromId(id);
		}
		
		/**
		 * @param id is value of addChild return
		 */
		public function removeChildAt(id:int):Boolean
		{
			var n:int=_frames.indexOf(id);
			if(n==-1) return false;
			removeFromId(id);
			return true;
		}
		
		protected function removeFromId(id:int):void
		{
			_cache[id]=null;
			
			for(var i:int=0; i<_frames.length; i++)
			{
				if(_frames[i] == id){
					_frames.splice(i, 1);
					i--;
				}
			}
			
			addDirectPoint();
		}
		
		public function removeAll():void
		{
			stop();
			initParams();
		}
		
		/**
		 * @param frame id 
		 * @param com.shine.type.AnimationClipCommand
		 */
		public function addFrameLabel(frame:uint,command:uint=0,label:String=""):void
		{
			_commands[frame]={type:command,label:label};
		}
		
		public function removeFrameLabel(frame:uint):void
		{
			delete _commands[frame];
		}
		
		public function getFrameTarget():DisplayObject
		{
			if(_cache.length<1) return null;
			return _cache[_frames[_current_index]];
		}
		
		public function dispose():void
		{
			stop();
			initParams();
			_container.removeEventListener(Event.ADDED_TO_STAGE,addToStageHandler);
			_container.removeEventListener(Event.REMOVED_FROM_STAGE,removeToStageHandler);
		}
		
		protected function addDirectPoint():void
		{
			_points_next=new Vector.<uint>();
			_points_prec=new Vector.<uint>();
			
			if(_frames.length<1) return;
			for(var i:int=0;i<_frames.length;i++)
			{
				_points_next.push(i+1);
				_points_prec.push(i-1);
			}
			
			_points_prec[0]=_frames.length-1;
			_points_next[_frames.length-1]=0;
			
		}
		
		protected function check_MC_label(child:DisplayObject):void
		{
			if(child==null || !(child is MovieClip))return;
			var mc:MovieClip=MovieClip(child);
			for(var i:uint=0;i<mc.totalFrames;i++){
				mc.gotoAndStop(i+1);
				if(!StringUtil.isEmpty(mc.currentFrameLabel)){
					addFrameLabel(i+1,AnimationClipCommand.DISPATCH_EVENT,mc.currentFrameLabel);
				}
			}
		}
		
		protected function initParams():void
		{
			_current_index=0;
			_commands=new Dictionary();
			_container=new SpriteBase();
			_myTimer = new Timer(AnimationClip.interval,0);
			
			_cache =new Vector.<DisplayObject>();
			_frames=new Vector.<uint>();
			
			_points_next=new Vector.<uint>();
			_points_prec=new Vector.<uint>();
			
			_max_width=0;
			_max_height=0;
			
			_current_child=null;
			_current_count=0;
			_clockwise=true;
			_enabled=false;
			_skip=true;
		}
		
		public function clone(C:Class=null):IAnimationClip
		{
			var copy:IAnimationClip;
			if(C==null){
				copy=new AnimationClip();
			}else{
				copy=new C();
			}
			
			DisplayObjectUtil.copyAttribute(copy.container,container);
			
			for(var i:uint=0;i<_cache.length;i++){
				var l:int=getTargetLength(i);
				if(l!=-1){
					var item:DisplayObject=_cache[i] as DisplayObject;
					copy.addChild(item,l);
				}
			}
			return copy;
		}
		
		protected function getTargetLength(id:uint):int
		{
			var i:int=_frames.indexOf(id);
			if(i==-1)return -1;
			var l:uint=0;
			for(i;i<_frames.length;i++){
				if(_frames[i]==id) l++;
			}
			return l;
		}
		
		protected function resetSizeValue(child:DisplayObject):void
		{
			_max_width=format(Math.max(_max_width,child.width));
			_max_height=format(Math.max(_max_height,child.height));
		}
		
		protected function format(num:Number):Number
		{
			return Number(num.toFixed(2));
		}
		
		protected static var interval:uint=50;
		
		public static function set RATE(n:uint):void
		{
			if(n>0){
				interval=uint(Math.round(1000/n));
			}
		}
		
		public static function get RATE():uint
		{
			return uint(1000/interval);
		}
		
		public static function set INTERVAL(n:uint):void
		{
			if(n>0)interval=n;
		}
		
		override public function toString():String
		{
			return ClassUtil.toString(this,"currentFrame","totalFrames","maxWidth","maxHeight","container","loop");
		}
		
	}
}